Turn --git on by default for `cargo-new`.
authorAlex Crichton <alex@alexcrichton.com>
Thu, 28 Aug 2014 20:22:36 +0000 (13:22 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 3 Sep 2014 21:00:16 +0000 (14:00 -0700)
This adds a command-line --no-git option to disable this behavior, as well as
adding a global config section for `git = false`. While I was at it I write some
documentation for the configuration format that cargo uses.

src/bin/new.rs
src/cargo/ops/cargo_new.rs
src/cargo/util/config.rs
src/doc/source/config.md [new file with mode: 0644]
src/doc/source/guide.md
src/doc/source/index.md
src/doc/source/layouts/layout.erb
tests/test_cargo_new.rs

index a1e72c1bddab5420762ef5fdc1732e4e0a363de4..c2e56945aa1b2fa086a384d74d3c6ef9aae03a0d 100644 (file)
@@ -14,7 +14,9 @@ Usage:
 
 Options:
     -h, --help          Print this message
-    --git               Initialize a new git repository with a .gitignore
+    --no-git            Don't initialize a new git repository
+    --git               Initialize a new git repository, overriding a
+                        global `git = false` configuration
     --bin               Use a binary instead of a library template
     -v, --verbose       Use verbose output
 ")
@@ -23,9 +25,10 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
     debug!("executing; cmd=cargo-new; args={}", os::args());
     shell.set_verbose(options.flag_verbose);
 
-    let Options { flag_git, flag_bin, arg_path, .. } = options;
+    let Options { flag_no_git, flag_bin, arg_path, flag_git, .. } = options;
 
     let opts = ops::NewOptions {
+        no_git: flag_no_git,
         git: flag_git,
         path: arg_path.as_slice(),
         bin: flag_bin,
index 9ff86e79bda7f68990182f39756d5cc235ee40fd..85a60c5ae8dd4822fd4d846900d76031312e4a45 100644 (file)
@@ -3,15 +3,22 @@ use std::io::{mod, fs, File};
 
 use git2::{Repository, Config};
 
-use util::{CargoResult, human, ChainError};
+use util::{CargoResult, human, ChainError, config, internal};
 use core::shell::MultiShell;
 
 pub struct NewOptions<'a> {
+    pub no_git: bool,
     pub git: bool,
     pub bin: bool,
     pub path: &'a str,
 }
 
+struct CargoNewConfig {
+    name: Option<String>,
+    email: Option<String>,
+    git: Option<bool>,
+}
+
 pub fn new(opts: NewOptions, _shell: &mut MultiShell) -> CargoResult<()> {
     let path = os::getcwd().join(opts.path);
     if path.exists() {
@@ -26,19 +33,29 @@ pub fn new(opts: NewOptions, _shell: &mut MultiShell) -> CargoResult<()> {
 }
 
 fn mk(path: &Path, name: &str, opts: &NewOptions) -> CargoResult<()> {
-
-    if opts.git {
+    let cfg = try!(global_config());
+    if !opts.git && (opts.no_git || cfg.git == Some(false)) {
+        try!(fs::mkdir(path, io::UserRWX));
+    } else {
         try!(Repository::init(path));
         let mut gitignore = "/target\n".to_string();
         if !opts.bin {
             gitignore.push_str("/Cargo.lock\n");
         }
         try!(File::create(&path.join(".gitignore")).write(gitignore.as_bytes()));
-    } else {
-        try!(fs::mkdir(path, io::UserRWX));
     }
 
-    let author = try!(discover_author());
+    let (author_name, email) = try!(discover_author());
+    // Hoo boy, sure glad we've got exhaustivenes checking behind us.
+    let author = match (cfg.name, cfg.email, author_name, email) {
+        (Some(name), Some(email), _, _) |
+        (Some(name), None, _, Some(email)) |
+        (None, Some(email), name, _) |
+        (None, None, name, Some(email)) => format!("{} <{}>", name, email),
+        (Some(name), None, _, None) |
+        (None, None, name, None) => name,
+    };
+
     try!(File::create(&path.join("Cargo.toml")).write_str(format!(
 r#"[package]
 
@@ -66,7 +83,7 @@ fn it_works() {
     Ok(())
 }
 
-fn discover_author() -> CargoResult<String> {
+fn discover_author() -> CargoResult<(String, Option<String>)> {
     let git_config = Config::open_default().ok();
     let git_config = git_config.as_ref();
     let name = git_config.and_then(|g| g.get_str("user.name").ok())
@@ -82,8 +99,46 @@ fn discover_author() -> CargoResult<String> {
     let name = name.as_slice().trim().to_string();
     let email = email.map(|s| s.as_slice().trim().to_string());
 
-    Ok(match (name, email) {
-        (name, Some(email)) => format!("{} <{}>", name, email),
-        (name, None) => name,
-    })
+    Ok((name, email))
+}
+
+fn global_config() -> CargoResult<CargoNewConfig> {
+    let user_configs = try!(config::all_configs(os::getcwd()));
+    let mut cfg = CargoNewConfig {
+        name: None,
+        email: None,
+        git: None,
+    };
+    let cargo_new = match user_configs.find_equiv(&"cargo-new") {
+        None => return Ok(cfg),
+        Some(target) => try!(target.table().chain_error(|| {
+            internal("invalid configuration for the key `cargo-new`")
+        })),
+    };
+    cfg.name = match cargo_new.find_equiv(&"name") {
+        None => None,
+        Some(name) => {
+            Some(try!(name.string().chain_error(|| {
+                internal("invalid configuration for key `cargo-new.name`")
+            })).ref0().to_string())
+        }
+    };
+    cfg.email = match cargo_new.find_equiv(&"email") {
+        None => None,
+        Some(email) => {
+            Some(try!(email.string().chain_error(|| {
+                internal("invalid configuration for key `cargo-new.email`")
+            })).ref0().to_string())
+        }
+    };
+    cfg.git = match cargo_new.find_equiv(&"git") {
+        None => None,
+        Some(git) => {
+            Some(try!(git.boolean().chain_error(|| {
+                internal("invalid configuration for key `cargo-new.git`")
+            })).val0())
+        }
+    };
+
+    Ok(cfg)
 }
index 2747c8f8aa8c25ff610c3dce66ee7f288c487c08..cf1cf419e20d2c49d16cd28d170518177f203f67 100644 (file)
@@ -81,6 +81,7 @@ pub enum ConfigValue {
     String(String, Path),
     List(Vec<(String, Path)>),
     Table(HashMap<String, ConfigValue>),
+    Boolean(bool, Path),
 }
 
 impl fmt::Show for ConfigValue {
@@ -98,6 +99,7 @@ impl fmt::Show for ConfigValue {
                 write!(f, "]")
             }
             Table(ref table) => write!(f, "{}", table),
+            Boolean(b, ref path) => write!(f, "{} (from {})", b, path.display()),
         }
     }
 }
@@ -111,6 +113,7 @@ impl<E, S: Encoder<E>> Encodable<S, E> for ConfigValue {
                 list.encode(s)
             }
             Table(ref table) => table.encode(s),
+            Boolean(b, _) => b.encode(s),
         }
     }
 }
@@ -119,6 +122,7 @@ impl ConfigValue {
     fn from_toml(path: &Path, toml: toml::Value) -> CargoResult<ConfigValue> {
         match toml {
             toml::String(val) => Ok(String(val, path.clone())),
+            toml::Boolean(b) => Ok(Boolean(b, path.clone())),
             toml::Array(val) => {
                 Ok(List(try!(result::collect(val.move_iter().map(|toml| {
                     match toml {
@@ -140,6 +144,7 @@ impl ConfigValue {
     fn merge(&mut self, from: ConfigValue) -> CargoResult<()> {
         match (self, from) {
             (me @ &String(..), from @ String(..)) => *me = from,
+            (me @ &Boolean(..), from @ Boolean(..)) => *me = from,
             (&List(ref mut old), List(ref mut new)) => {
                 let new = mem::replace(new, Vec::new());
                 old.extend(new.move_iter());
@@ -165,25 +170,33 @@ impl ConfigValue {
 
     pub fn string(&self) -> CargoResult<(&str, &Path)> {
         match *self {
-            Table(..) => Err(internal("expected a string, but found a table")),
-            List(..) => Err(internal("expected a string, but found a list")),
             String(ref s, ref p) => Ok((s.as_slice(), p)),
+            _ => Err(internal(format!("expected a string, but found a {}",
+                                      self.desc()))),
         }
     }
 
     pub fn table(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
         match *self {
-            String(..) => Err(internal("expected a table, but found a string")),
-            List(..) => Err(internal("expected a table, but found a list")),
             Table(ref table) => Ok(table),
+            _ => Err(internal(format!("expected a table, but found a {}",
+                                      self.desc()))),
         }
     }
 
     pub fn list(&self) -> CargoResult<&[(String, Path)]> {
         match *self {
-            String(..) => Err(internal("expected a list, but found a string")),
-            Table(..) => Err(internal("expected a list, but found a table")),
             List(ref list) => Ok(list.as_slice()),
+            _ => Err(internal(format!("expected a list, but found a {}",
+                                      self.desc()))),
+        }
+    }
+
+    pub fn boolean(&self) -> CargoResult<(bool, &Path)> {
+        match *self {
+            Boolean(b, ref p) => Ok((b, p)),
+            _ => Err(internal(format!("expected a bool, but found a {}",
+                                      self.desc()))),
         }
     }
 
@@ -192,6 +205,7 @@ impl ConfigValue {
             Table(..) => "table",
             List(..) => "array",
             String(..) => "string",
+            Boolean(..) => "boolean",
         }
     }
 }
diff --git a/src/doc/source/config.md b/src/doc/source/config.md
new file mode 100644 (file)
index 0000000..efc0bef
--- /dev/null
@@ -0,0 +1,69 @@
+---
+title: Configuration
+---
+
+This document will explain how cargo's configuration system works, as well as
+available keys or configuration.  For configuration of a project through its
+manfest, see the [manifest format](manifest.html).
+
+# Hierarchical structure
+
+Cargo allows to have local configuration for a particular project or global
+configuration (like git). Cargo also extends this ability to a hirearchical
+strategy. If, for example, cargo were invoked in `/home/foo/bar/baz`, then the
+following configuration files would be probed for:
+
+* `/home/foo/bar/baz/.cargo/config`
+* `/home/foo/bar/.cargo/config`
+* `/home/foo/.cargo/config`
+* `/home/.cargo/config`
+* `/.cargo/config`
+
+With this structure you can specify local configuration per-project, and even
+possibly check it into version control. You can also specify personal default
+with a configuration file in your home directory.
+
+# Configuration Format
+
+All configuration is currently in the TOML format (like the manifest), with
+simple key-value pairs inside of sections (tables) which all get merged
+together.
+
+# Configuration keys
+
+All of the following keys are optional, and their defaults are listed as their
+value unless otherwise noted.
+
+```toml
+# An array of paths to local repositories which are to be used as overrides for
+# dependencies. For more information see the Cargo Guide.
+paths = [ "/path/to/override" ]
+
+[cargo-new]
+# This is your name/email to place in the `authors` section of a new Cargo.toml
+# that is generated. If not present, then `git` will be probed, and if that is
+# not present then `$USER` will be used (with no email).
+name = "..."
+email = "..."
+
+# By default `cargo new` will initialize a new git repository. This key can be
+# set to `false` to disable this behavior.
+git = true
+
+# For the following sections, $triple refers to any valid target triple, not the
+# literal string "$triple", and it will apply whenever that target triple is
+# being compiled to.
+[target]
+
+# For cargo builds which do not mention --target, these are the ar/linker which
+# are passed to rustc to use (via `-C ar=` and `-C linker=`). By default these
+# flags are not passed to the compiler.
+ar = ".."
+linker = ".."
+
+[target.$triple]
+# Similar to the above ar/linker configuration, but this only applies to when
+# the `$triple` is being compiled for.
+ar = ".."
+linker = ".."
+```
index cced5cfafb6042b9057ebc18904c203f482b4fe7..a57df78b49dee1f927107647bb866f3bf383ba09 100644 (file)
@@ -34,7 +34,8 @@ $ cargo new hello_world --bin
 ```
 
 We're passing `--bin` because we're making a binary program: if we
-were making a library, we'd leave it off.
+were making a library, we'd leave it off. If you'd like to not initialize a new
+git repository as well (the default), you can also pass `--no-git`.
 
 Let's check out what Cargo has generated for us:
 
@@ -359,6 +360,9 @@ This array should be filled with directories that contain a `Cargo.toml`. In
 this instance, we're just adding `conduit`, so it will be the only one that's
 overridden.
 
+More information about local configuration can be found in the [configuration
+documentation](config.html).
+
 # Tests
 
 Cargo can run your tests with the `cargo test` command. Cargo runs tests in two
index f5a6eaa71455ba89949e8c756678e0ae4e699a4c..cbc627326ccb1d46bd20e93b87104f39248603a3 100644 (file)
@@ -24,12 +24,11 @@ Alternatively, you can build Cargo from source.
 To start a new project with Cargo, use `cargo new`:
 
 ```shell
-$ cargo new hello_world --bin --git
+$ cargo new hello_world --bin
 ```
 
 We're passing `--bin` because we're making a binary program: if we
-were making a library, we'd leave it off. We also pass `--git` to auto-generate
-a `.gitignore` and set up the git repository, but nothing will be committed.
+were making a library, we'd leave it off.
 
 Let's check out what Cargo has generated for us:
 
index 4c6a8d983792fec85127bc141d8daab1b8012c8d..e1555473b09fed111254a2815f21a61d72cee977 100644 (file)
@@ -2,17 +2,17 @@
 <html>
   <head>
     <meta charset="utf-8">
-    
+
     <!-- Always force latest IE rendering engine or request Chrome Frame -->
     <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
-    
+
     <!-- Use title if it's in the page YAML frontmatter -->
     <title><%= current_page.data.title || "The Middleman" %></title>
-    
+
     <%= stylesheet_link_tag "all" %>
     <%= javascript_include_tag  "all" %>
   </head>
-  
+
   <body class="<%= page_classes %>">
     <%= link_to image_tag("forkme.png", class: "fork-me"), "https://github.com/rust-lang/cargo" %>
     <%= link_to image_tag("Cargo-Logo-Small.png", class: "logo"), "index.html" %>
@@ -26,7 +26,8 @@
         <%= link_to "Guide", "guide.html" %> |
         <%= link_to "Frequently Asked Questions", "faq.html" %> |
         <%= link_to "Manifest Format", "manifest.html" %> |
-        <%= link_to "Building Non-Rust Code", "native-build.html" %>
+        <%= link_to "Building Non-Rust Code", "native-build.html" %> |
+        <%= link_to "Configuration", "config.html" %>
       </footer>
     </main>
 
index a60f7a71268a8b0145c53d16d9e64796857d2038..dd469649391e9ea312e7fc76cfe5216504935957 100644 (file)
@@ -2,7 +2,7 @@ use std::io::{fs, UserRWX, File};
 use std::os;
 
 use support::{execs, paths, cargo_dir, ResultTest};
-use hamcrest::{assert_that, existing_file, existing_dir};
+use hamcrest::{assert_that, existing_file, existing_dir, is_not};
 
 use cargo::util::{process, ProcessBuilder};
 
@@ -23,12 +23,13 @@ fn cargo_process(s: &str) -> ProcessBuilder {
 
 test!(simple_lib {
     os::setenv("USER", "foo");
-    assert_that(cargo_process("new").arg("foo"),
+    assert_that(cargo_process("new").arg("foo").arg("--no-git"),
                 execs().with_status(0));
 
     assert_that(&paths::root().join("foo"), existing_dir());
     assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
     assert_that(&paths::root().join("foo/src/lib.rs"), existing_file());
+    assert_that(&paths::root().join("foo/.gitignore"), is_not(existing_file()));
 
     assert_that(cargo_process("build").cwd(paths::root().join("foo")),
                 execs().with_status(0));
@@ -52,7 +53,7 @@ test!(simple_bin {
 
 test!(simple_git {
     os::setenv("USER", "foo");
-    assert_that(cargo_process("new").arg("foo").arg("--git"),
+    assert_that(cargo_process("new").arg("foo"),
                 execs().with_status(0));
 
     assert_that(&paths::root().join("foo"), existing_dir());
@@ -105,3 +106,42 @@ test!(finds_author_git {
     let toml = File::open(&toml).read_to_string().assert();
     assert!(toml.as_slice().contains(r#"authors = ["bar <baz>"]"#));
 })
+
+test!(author_prefers_cargo {
+    my_process("git").args(["config", "--global", "user.name", "bar"])
+                     .exec().assert();
+    my_process("git").args(["config", "--global", "user.email", "baz"])
+                     .exec().assert();
+    let root = paths::root();
+    fs::mkdir(&root.join(".cargo"), UserRWX).assert();
+    File::create(&root.join(".cargo/config")).write_str(r#"
+        [cargo-new]
+        name = "new-foo"
+        email = "new-bar"
+        git = false
+    "#).assert();
+
+    assert_that(cargo_process("new").arg("foo").env("USER", Some("foo")),
+                execs().with_status(0));
+
+    let toml = paths::root().join("foo/Cargo.toml");
+    let toml = File::open(&toml).read_to_string().assert();
+    assert!(toml.as_slice().contains(r#"authors = ["new-foo <new-bar>"]"#));
+    assert!(!root.join("foo/.gitignore").exists());
+})
+
+test!(git_prefers_command_line {
+    let root = paths::root();
+    fs::mkdir(&root.join(".cargo"), UserRWX).assert();
+    File::create(&root.join(".cargo/config")).write_str(r#"
+        [cargo-new]
+        git = false
+        name = "foo"
+        email = "bar"
+    "#).assert();
+
+    assert_that(cargo_process("new").arg("foo").arg("--git")
+                                    .env("USER", Some("foo")),
+                execs().with_status(0));
+    assert!(root.join("foo/.gitignore").exists());
+})